/*
 *==========================================================================
 *      xyzModem.c
 *      RedBoot stream handler for xyzModem protocol
 *==========================================================================
 * Author(s):    gthomas
 * Contributors: gthomas, tsmith, Yoshinori Sato
 * Date:         2000-07-14
 * Purpose:
 * Description:
 * This code is part of RedBoot (tm).
 */
#include "xyzModem.h"
#include <string.h>
#include "ymodem_port.h"
#include "sline.h"

/* Assumption - run xyzModem protocol over the console port */
/* Values magic to the protocol */
#define SOH 0x01U
#define STX 0x02U
#define SOT 0x04U
#define ACK 0x06U
#define BSP 0x08U
#define NAK 0x15U
#define CAN 0x18U
#define XYZ_EOF 0x1AU		/* ^Z for DOS officionados */

#define USER_FLASH_SIZE         (1024)
#define EOT                     (0x04)  /* end of transmission */
#define PACKET_HEADER           (3)
#define PACKET_TRAILER          (2)
#define PACKET_OVERHEAD         (PACKET_HEADER + PACKET_TRAILER)
#define FILE_NAME_LENGTH        (256)
#define FILE_SIZE_LENGTH        (16)
#define PACKET_SIZE             (128)
#define PACKET_1K_SIZE          (1024)
#define CRC16                   (0x43)  /* 'C' == 0x43, request 16-bit CRC */

#define xyzModem_MAX_RETRIES             20
#define xyzModem_MAX_RETRIES_WITH_CRC    10
#define xyzModem_CAN_COUNT                3	/* Wait for 3 CAN before quitting */

#define DELAY 20
/* Data & state local to the protocol */
extern unsigned short cyg_crc16(unsigned char *buf, int len);
static struct {
  int *__chan;
  unsigned char pkt[1024], *bufp;
  unsigned char blk, cblk, crc1, crc2;
  unsigned char next_blk;	/* Expected block */
  int len, mode, total_retries;
  int total_SOH, total_STX, total_CAN;
  int crc_mode, at_eof, tx_ack;
  unsigned long file_length, read_length;
} xyz;

typedef int cyg_int32;

/* 等待初始化 ymodem 结构体 等待初始化*/
static ymodem_port_st ym = {.tschar = NULL,
                    .getc = NULL,
                    .putc = NULL,
                    .usdelay = NULL};

static const unsigned short crc16_tab[] = {
    0x0000U, 0x1021U, 0x2042U, 0x3063U, 0x4084U, 0x50a5U, 0x60c6U, 0x70e7U,
    0x8108U, 0x9129U, 0xa14aU, 0xb16bU, 0xc18cU, 0xd1adU, 0xe1ceU, 0xf1efU,
    0x1231U, 0x0210U, 0x3273U, 0x2252U, 0x52b5U, 0x4294U, 0x72f7U, 0x62d6U,
    0x9339U, 0x8318U, 0xb37bU, 0xa35aU, 0xd3bdU, 0xc39cU, 0xf3ffU, 0xe3deU,
    0x2462U, 0x3443U, 0x0420U, 0x1401U, 0x64e6U, 0x74c7U, 0x44a4U, 0x5485U,
    0xa56aU, 0xb54bU, 0x8528U, 0x9509U, 0xe5eeU, 0xf5cfU, 0xc5acU, 0xd58dU,
    0x3653U, 0x2672U, 0x1611U, 0x0630U, 0x76d7U, 0x66f6U, 0x5695U, 0x46b4U,
    0xb75bU, 0xa77aU, 0x9719U, 0x8738U, 0xf7dfU, 0xe7feU, 0xd79dU, 0xc7bcU,
    0x48c4U, 0x58e5U, 0x6886U, 0x78a7U, 0x0840U, 0x1861U, 0x2802U, 0x3823U,
    0xc9ccU, 0xd9edU, 0xe98eU, 0xf9afU, 0x8948U, 0x9969U, 0xa90aU, 0xb92bU,
    0x5af5U, 0x4ad4U, 0x7ab7U, 0x6a96U, 0x1a71U, 0x0a50U, 0x3a33U, 0x2a12U,
    0xdbfdU, 0xcbdcU, 0xfbbfU, 0xeb9eU, 0x9b79U, 0x8b58U, 0xbb3bU, 0xab1aU,
    0x6ca6U, 0x7c87U, 0x4ce4U, 0x5cc5U, 0x2c22U, 0x3c03U, 0x0c60U, 0x1c41U,
    0xedaeU, 0xfd8fU, 0xcdecU, 0xddcdU, 0xad2aU, 0xbd0bU, 0x8d68U, 0x9d49U,
    0x7e97U, 0x6eb6U, 0x5ed5U, 0x4ef4U, 0x3e13U, 0x2e32U, 0x1e51U, 0x0e70U,
    0xff9fU, 0xefbeU, 0xdfddU, 0xcffcU, 0xbf1bU, 0xaf3aU, 0x9f59U, 0x8f78U,
    0x9188U, 0x81a9U, 0xb1caU, 0xa1ebU, 0xd10cU, 0xc12dU, 0xf14eU, 0xe16fU,
    0x1080U, 0x00a1U, 0x30c2U, 0x20e3U, 0x5004U, 0x4025U, 0x7046U, 0x6067U,
    0x83b9U, 0x9398U, 0xa3fbU, 0xb3daU, 0xc33dU, 0xd31cU, 0xe37fU, 0xf35eU,
    0x02b1U, 0x1290U, 0x22f3U, 0x32d2U, 0x4235U, 0x5214U, 0x6277U, 0x7256U,
    0xb5eaU, 0xa5cbU, 0x95a8U, 0x8589U, 0xf56eU, 0xe54fU, 0xd52cU, 0xc50dU,
    0x34e2U, 0x24c3U, 0x14a0U, 0x0481U, 0x7466U, 0x6447U, 0x5424U, 0x4405U,
    0xa7dbU, 0xb7faU, 0x8799U, 0x97b8U, 0xe75fU, 0xf77eU, 0xc71dU, 0xd73cU,
    0x26d3U, 0x36f2U, 0x0691U, 0x16b0U, 0x6657U, 0x7676U, 0x4615U, 0x5634U,
    0xd94cU, 0xc96dU, 0xf90eU, 0xe92fU, 0x99c8U, 0x89e9U, 0xb98aU, 0xa9abU,
    0x5844U, 0x4865U, 0x7806U, 0x6827U, 0x18c0U, 0x08e1U, 0x3882U, 0x28a3U,
    0xcb7dU, 0xdb5cU, 0xeb3fU, 0xfb1eU, 0x8bf9U, 0x9bd8U, 0xabbbU, 0xbb9aU,
    0x4a75U, 0x5a54U, 0x6a37U, 0x7a16U, 0x0af1U, 0x1ad0U, 0x2ab3U, 0x3a92U,
    0xfd2eU, 0xed0fU, 0xdd6cU, 0xcd4dU, 0xbdaaU, 0xad8bU, 0x9de8U, 0x8dc9U,
    0x7c26U, 0x6c07U, 0x5c64U, 0x4c45U, 0x3ca2U, 0x2c83U, 0x1ce0U, 0x0cc1U,
    0xef1fU, 0xff3eU, 0xcf5dU, 0xdf7cU, 0xaf9bU, 0xbfbaU, 0x8fd9U, 0x9ff8U,
    0x6e17U, 0x7e36U, 0x4e55U, 0x5e74U, 0x2e93U, 0x3eb2U, 0x0ed1U, 0x1ef0U,
};

void set_ymodem_port(ymodem_port_st* ym_st)
{
    ym.putc = ym_st->putc;
    ym.getc = ym_st->getc;
    ym.usdelay = ym_st->usdelay;
    ym.tschar = ym_st->tschar;
}

unsigned short cyg_crc16(unsigned char buf[], int len)
{
    unsigned int i;
    unsigned short cksum;

    cksum = (unsigned short)0U;
    for (i = (unsigned int)0U;  i < (unsigned int)len;  i++) {
	    cksum = crc16_tab[((cksum>>8U) ^ (unsigned short)buf[i]) & (unsigned short)0xFFU] ^ (cksum << 8U);
    }
    return cksum;
}

static int CYGACC_COMM_IF_GETC_TIMEOUT (char chan,unsigned char *c)
{
  int ret = 0;
  unsigned long counter = 0UL;
  while ((ym.tschar() == 0)){
      if(counter >= 40000UL){
          break;
      }
      ym.usdelay (1);
      counter++;
  }
  if (ym.tschar () != 0) {
      *c = (unsigned char)ym.getc ();
      ret = 1;
    }
  return ret;
}

static void CYGACC_COMM_IF_PUTC (char x, char y)
{
  ym.putc (y);
}

/* Validate a hex character */
__inline__ static int _is_hex (char c)
{
    unsigned char ret = 0U;
    unsigned char tmp = 0U;
    tmp = (unsigned char)c;

    ret = (unsigned char)((tmp >= 0x30U) && (tmp <= 0x39U));
    ret |= (unsigned char)((tmp >= 0x41U) && (tmp <= 0x46U));
    ret |= (unsigned char)((tmp >= 0x61U) && (tmp <= 0x66U));
    return (int)ret;
}

/* Convert a single hex nibble */
__inline__ static unsigned long  _from_hex (char c)
{
    unsigned char ret = 0U;
    unsigned char tmp = 0U;
    tmp = (unsigned char)c;

  if ((tmp >= 0x30U) && (tmp <= 0x39U)) {
      ret = (tmp - 0x30U);
    } else if ((tmp >= 0x61U) && (tmp <= 0x66U)) {
      ret = (tmp - 0x57U);
    } else if ((tmp >= 0x41U) && (tmp <= 0x46U)) {
      ret = (tmp - 0x37U);
    } else{
      ret = 0U;
  }
  return (unsigned long)ret;
}

/* Convert a character to lower case */
__inline__ static unsigned char _tolower (unsigned char c)
{
    unsigned char tmp = 0U;
    tmp = (unsigned char)c;
    if ((tmp >= 0x41U) && (tmp <= 0x5aU)){
        tmp += 0x20U;
    }
    c = tmp;
    return c;
}

/* Parse (scan) a number */
static int parse_num (unsigned char s[], unsigned long *val, char **es, const char delim[])
{
  int first = y_true,ret = y_true;
  unsigned long radix = 10UL;
  char c;
  unsigned char tmp_d,tmp_c;
  unsigned long result = 0UL, digit;
  int i = 0,j = 0;

  while (s[i] == 0x20U){
    i++;
  }
  while ((signed char)s[i] != 0){
      if ((first == y_true)){
          if(s[i] == 0x30U){
              if(_tolower (s[i+1]) == 120U){
                  radix = 16U;
                  i += 2;
              }
          }
      }
      first = y_false;
      c = (char)s[i];
      i++;
      if (_is_hex (c) != 0){
          digit = _from_hex (c);
          if((digit) < radix){
              /* Valid digit */
              result = (unsigned long)((result * radix) + digit);
          }
      } else{
          if (delim != (char *) 0){
              /* See if this character is one of the delimiters */
              do{
                  tmp_d = (c != delim[j])?1U:0U;
                  tmp_c = ((signed char)delim[j] != 0)?1U:0U;
                  tmp_d = tmp_d & tmp_c;
                  j++;
              }while (tmp_d != 0U);
              if ((signed char)delim[j] != 0){
                  ret = 2;
              }
          }
          if(ret == 2){
              ret = y_true;
          } else{
              ret = y_false;		/* Malformatted number */
          }
          break;
      }
  }
  if(ret == y_true){
      *val = result;
      if (es != (char **) 0){
          *es = (char *)s;
      }
  }
  return ret;
}
/* Wait for the line to go idle */
static void xyzModem_flush (void)
{
  int res;
  unsigned char *c;
  unsigned char tmp_c = SOT;
  c = &tmp_c;
  while (res != 0){
      res = CYGACC_COMM_IF_GETC_TIMEOUT ((char)(*(xyz.__chan)), (unsigned char *)(c));
  }
  return;
}

static int xyzModem_get_hdr (void)
{
  unsigned char *c;
  unsigned char tmp_c = SOT;
  int res,ret = 0;
  int hdr_found = y_false;
  int i, can_total, hdr_chars;
  unsigned short cksum,tmp_crc;

  /* Find the start of a header */
  can_total = 0;
  hdr_chars = 0;
  c = &tmp_c;
  if (xyz.tx_ack != 0){
      CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)ACK);
      xyz.tx_ack = y_false;
  }
  while (hdr_found == 0){
      res = CYGACC_COMM_IF_GETC_TIMEOUT((char)(*xyz.__chan),(unsigned char *)(c));
      if (res != 0){
          hdr_chars++;
          switch (*c){
              case SOH:
                  xyz.total_SOH++;
                  hdr_found = y_true;
                  break;
                /* null */
              case STX:
                  if (*c == STX){
                      xyz.total_STX++;
                  }
                  hdr_found = y_true;
                  break;
              case CAN:
                  xyz.total_CAN++;
                  if ((++can_total) == xyzModem_CAN_COUNT){
                      ret = xyzModem_cancel;
                  }
                  break;
                /* null */
              case SOT:
                  /* SOT only supported if no noise */
                  if (hdr_chars == 1){
                      CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)ACK);
                      ret = xyzModem_eof;
                  }
                  break;
              default:
                  /* Ignore, waiting for start of header */
                  break;
          }
      }
      else{
          /* Data stream timed out */
          xyzModem_flush ();	/* Toss any current input */
          ym.usdelay((cyg_int32) 250000);
          ret = xyzModem_timeout;
      }
      if(ret < 0){
          break;
      }
  }
  if (ret >= 0){
  /* Header found, now read the data */
      res = CYGACC_COMM_IF_GETC_TIMEOUT ((char)(*(xyz.__chan)), (unsigned char *) (&(xyz.blk)));
      if (res == 0){
          ret = xyzModem_timeout;
      } else{
          res = CYGACC_COMM_IF_GETC_TIMEOUT ((char)(*xyz.__chan), (unsigned char *) (&xyz.cblk));
          if (res == 0){
              ret = xyzModem_timeout;
          } else{
              xyz.len = (int)(((*c) == SOH) ? 128L : 1024L);
              xyz.bufp = xyz.pkt;
              for (i = 0; i < xyz.len; i++){
                  res = CYGACC_COMM_IF_GETC_TIMEOUT ((char)(*xyz.__chan), (unsigned char *)(c));
                  if (res != 0){
                      xyz.pkt[i] = (unsigned char)*c;
                  } else{
                      ret = xyzModem_timeout;
                  }
              }
              res = CYGACC_COMM_IF_GETC_TIMEOUT ((char)(*xyz.__chan), (unsigned char *) (&xyz.crc1));
              if (res == 0){
                  ret = xyzModem_timeout;
              } else{
                  if ((xyz.crc_mode != 0)){
                      res = CYGACC_COMM_IF_GETC_TIMEOUT ((char)(*xyz.__chan), (unsigned char *) (&xyz.crc2));
                      if (res == 0){
                          ret = xyzModem_timeout;
                      }
                  }
                  if(ret >= 0){
                  /* Validate the message */
                      if ((xyz.blk ^ xyz.cblk) != (unsigned char) 0xFFU){
                          xyzModem_flush ();
                          ret = xyzModem_frame;
                      } else{
                      /* Verify checksum/CRC */
                          if (xyz.crc_mode != 0){
                              cksum = cyg_crc16(xyz.pkt, xyz.len);
                              tmp_crc = (unsigned short)((unsigned short)(xyz.crc1) << 8U);
                              tmp_crc |= (unsigned short)(xyz.crc2);
                              if (cksum != tmp_crc){
                                  ret = xyzModem_cksum;
                              }
                          } else{
                              cksum = 0U;
                              for (i = 0; i < xyz.len; i++){
                                  cksum += xyz.pkt[i];
                              }
                              if ((xyz.crc1) != (cksum & 0xFFU)){
                                  ret = xyzModem_cksum;
                              }
                          }
                      }
                  }
              }
          }
      }
  }
  /* If we get here, the message passes [structural] muster */
  return ret;
}

static int getcxmodem(void) {
    int ret = -1;
	if (ym.tschar() != 0){
		ret = (int)(ym.getc());
    }
	return ret;
}

static int xyzModem_stream_open ( const connection_info_t * info, int *err)
{
  int stat = 0,i = 0,ret = -1;
  int retries = xyzModem_MAX_RETRIES;
  int crc_retries = xyzModem_MAX_RETRIES_WITH_CRC;


/* TODO: CHECK ! */
  static int dummy;
  xyz.__chan = &dummy;
  xyz.len = 0;
  xyz.crc_mode = y_true;
  xyz.at_eof = y_false;
  xyz.tx_ack = y_false;
  xyz.mode = info->mode;
  xyz.total_retries = 0;
  xyz.total_SOH = 0;
  xyz.total_STX = 0;
  xyz.total_CAN = 0;
  xyz.read_length = 0UL;
  xyz.file_length = 0UL;

  CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)(xyz.crc_mode ? 0x43 : NAK));

  if (xyz.mode == xyzModem_xmodem){
      /* X-modem doesn't have an information header - exit here */
      xyz.next_blk = 1U;
      ret = 0;
  } else{
      while (retries-- > 0){
          stat = xyzModem_get_hdr ();
          if (stat == 0){
        	  /* Y-modem file information header */
        	  if (xyz.blk == 0U){
                  /* skip filename */
        	      while (xyz.bufp[i] != 0U){
                      i++;
                  }
                  /* get the length */
        	      parse_num ( xyz.bufp, &xyz.file_length, (char **) 0, " ");
        	      /* The rest of the file name data block quietly discarded */
        	      xyz.tx_ack = y_true;
              }
              xyz.next_blk = 1U;
        	  xyz.len = 0;
        	  ret = 0;
              break;
          } else if (stat == xyzModem_timeout){
              if (--crc_retries <= 0){
                  xyz.crc_mode = y_false;
              }
              ym.usdelay(5 * 100000);	/* Extra delay for startup */
              CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)(xyz.crc_mode ? 0x43 : NAK));
              xyz.total_retries++;
          } else{
              ym.usdelay(1);
          }
      }
      if(ret != 0){
          *err = stat;
      }
  }
  return ret;
}

static int xyzModem_stream_read (char buf[], int size, int *err)
{
  int stat, total, len,ret = 0;
  int retries,tmp_len = 0;
  unsigned long tmp_addr;
  total = 0;
  stat = xyzModem_cancel;
  tmp_addr = (unsigned long)buf;
  /* Try and get 'size' bytes into the buffer */
  while ((xyz.at_eof == 0) && (size > 0)) {
      if (xyz.len == 0){
          retries = xyzModem_MAX_RETRIES;
          while ((retries--) > 0){
              stat = xyzModem_get_hdr ();
              if (stat == 0){
                  if (xyz.blk == xyz.next_blk){
                      xyz.tx_ack = y_true;
                      xyz.next_blk = ((xyz.next_blk + 1U) & 0xFFU);
                      if ( (xyz.file_length == 0U)){
                          /* Data blocks can be padded with ^Z (XYZ_EOF) characters */
                          /* This code tries to detect and remove them */
                          if ((xyz.bufp[xyz.len - 1] == XYZ_EOF)){
                              if(xyz.bufp[xyz.len - 2] == XYZ_EOF){
                                  if(xyz.bufp[xyz.len - 3] == XYZ_EOF){
                                      while (xyz.len != 0){
                                          if(xyz.bufp[xyz.len - 1] != (unsigned char)XYZ_EOF){
                                              break;
                                          }
                                          xyz.len--;
                                      }
                                  }
                              }
                          }
                      }

		      /*
		       * See if accumulated length exceeds that of the file.
		       * If so, reduce size (i.e., cut out pad bytes)
		       * Only do this for Y-modem (and Z-modem should it ever
		       * be supported since it can fall back to Y-modem mode).
		       */
                      if (xyz.mode != xyzModem_xmodem){
                          if(0U != xyz.file_length){
                              xyz.read_length += (unsigned long)xyz.len;
                              if (xyz.read_length > xyz.file_length){
                                  tmp_len = (int)((int)xyz.read_length - (int)xyz.file_length);
                                  xyz.len -= tmp_len;
                              }
                          }
                      }
                      ret = 2;
                  } else if (xyz.blk == ((xyz.next_blk - 1U) & 0xFFU)){
                      /* Just re-ACK this so sender will get on with it */
                      CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)ACK);
                      ret = 1;
                  } else{
                      stat = xyzModem_sequence;
                  }
              }
              if (stat == xyzModem_cancel){
                  ret = 2;
              }
              if (stat == xyzModem_eof){
                  CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)ACK);
                  if (xyz.mode == xyzModem_ymodem){
                      CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan),(char)((xyz.crc_mode) ? 0x43 : NAK));
                      xyz.total_retries++;
                      stat = xyzModem_get_hdr ();
                      CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)ACK);
                  }
                  xyz.at_eof = y_true;
                  ret = 2;
              }
              if(ret == 0){
                  CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)(xyz.crc_mode ? 0x43 : NAK));
                  xyz.total_retries++;
              } else if(ret == 2){
                  break;
              }else{
                  ret = 0;
              }
          }
          if (stat < 0){
              *err = stat;
              xyz.len = -1;
              total = stat;
              break;
          }
      }
      /* Don't "read" data from the XYZ_EOF protocol package */
      if (xyz.at_eof == 0){
          len = xyz.len;
          if (size < len){
              len = size;
          }
          memcpy ((void *)(long)(tmp_addr), (xyz.bufp), len);
          tmp_addr += len;
          size -= len;
          total += len;
          xyz.len -= len;
          xyz.bufp += len;
      }
  }
  return total;
}

static void xyzModem_stream_close (int *err)
{
    sl_printf("\r\nymodem - %s mode, %d(SOH)/%d(STX)/%d(CAN) packets, %d retries\r\n",
    xyz.crc_mode ? "CRC" : "Cksum", xyz.total_SOH, xyz.total_STX,
    xyz.total_CAN, xyz.total_retries);
}

/* Need to be able to clean out the input buffer, so have to take the */
/* getc */
static void xyzModem_stream_terminate (int abort, int (*getc) (void))
{
  int c;

  if (abort != 0) {
    switch (xyz.mode) {
	case xyzModem_xmodem:
	case xyzModem_ymodem:
	  /* The X/YMODEM Spec seems to suggest that multiple CAN followed by an equal */
	  /* number of Backspaces is a friendly way to get the other end to abort. */
	  CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)CAN);
	  CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)CAN);
	  CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)CAN);
	  CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)CAN);
	  CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)BSP);
	  CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)BSP);
	  CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)BSP);
	  CYGACC_COMM_IF_PUTC ((char)(*xyz.__chan), (char)BSP);
	  /* Now consume the rest of what's waiting on the line. */
	  xyzModem_flush ();
	  xyz.at_eof = y_true;
	  break;
#ifdef xyzModem_zmodem
	case xyzModem_zmodem:
	  /* Might support it some day I suppose. */
#endif
    default: break;
	}
  } else {
      /*
       * Consume any trailing crap left in the inbuffer from
       * previous recieved blocks. Since very few files are an exact multiple
       * of the transfer block size, there will almost always be some gunk here.
       * If we don't eat it now, RedBoot will think the user typed it.
       */
        do{
            c = (*getc) ();
        }while(c > -1);
      /*
       * Make a small delay to give terminal programs like minicom
       * time to get control again after their file transfer program
       * exits.
       */
      ym.usdelay((cyg_int32) 250000);
    }
}

static char * xyzModem_error (int err)
{
    char *ret;
    switch (err) {
        case xyzModem_access:   ret = "Can't access file";               break;
        case xyzModem_noZmodem: ret = "Sorry, zModem not available yet"; break;
        case xyzModem_timeout:  ret = "Timed out";                       break;
        case xyzModem_eof:      ret = "End of file";                     break;
        case xyzModem_cancel:   ret = "Cancelled";                       break;
        case xyzModem_frame:    ret = "Invalid framing";                 break;
        case xyzModem_cksum:    ret = "CRC/checksum error";              break;
        case xyzModem_sequence: ret = "Block sequence error";            break;
        default:                ret = "Unknown error";                   break;
    }
  return ret;
}

/*
 * RedBoot interface
 */
int ymodem_recv (long offset)
{
	int size;
	int err;
	int res;
	connection_info_t info;
	char ymodemBuf[1024];
	long store_addr = (long)0xffffffffU;
	long addr = 0;

    /* 检查有没有注册函数 */
    if(ym.getc == NULL || ym.putc == NULL||
        ym.usdelay == NULL || ym.tschar == NULL)
    {
        return -1;
    }

	size = 0;
	info.mode = xyzModem_ymodem;
	res = xyzModem_stream_open (&info, &err);
        if(res < 0){
            size = -2;
        }
	if (res == 0) {
        res = xyzModem_stream_read (ymodemBuf, 1024, &err);
        if(res < 0){
            size = -3;
        }
        if(res == 0){
            size = -4;
        }
		while (res > 0) {
			store_addr = addr + offset;
			size += res;
			addr += res;
			memcpy ((void *)(long)store_addr, ymodemBuf,res);
            res = xyzModem_stream_read (ymodemBuf, 1024, &err);

		}
	} else {
		sl_printf("\r\n%s\r\n", xyzModem_error(err));
	}

	xyzModem_stream_close (&err);
	xyzModem_stream_terminate (y_false, &getcxmodem);

	return size;
}

/**
  * @brief  Convert an Integer to a string
  * @param  str: The string
  * @param  intnum: The intger to be converted
  * @retval None
  */
static void Int2Str(unsigned char* str, int intnum)
{
  unsigned int i, Div = 1000000000, j = 0, Status = 0;

  for (i = 0; i < 10; i++)
  {
    str[j++] = (intnum / Div) + 48;

    intnum = intnum % Div;
    Div /= 10;
    if ((str[j-1] == '0') & (Status == 0))
    {
      j = 0;
    }
    else
    {
      Status++;
    }
  }
}


/**
  * @brief  Prepare the first block
  * @param  timeout
  * @retval None
  */
static void Ymodem_PrepareIntialPacket(unsigned char *data, const unsigned char* fileName, unsigned int *length)
{
    unsigned short i, j;
    unsigned char file_ptr[10];

    /* Make first three packet */
    data[0] = SOH;
    data[1] = 0x00;
    data[2] = 0xff;

    /* Filename packet has valid data */
    for (i = 0; (fileName[i] != '\0') && (i < FILE_NAME_LENGTH);i++)
    {
        data[i + PACKET_HEADER] = fileName[i];
    }

    data[i + PACKET_HEADER] = 0x00;

    Int2Str (file_ptr, *length);
    for (j =0, i = i + PACKET_HEADER + 1; file_ptr[j] != '\0' ; )
    {
        data[i++] = file_ptr[j++];
    }

    for (j = i; j < PACKET_SIZE + PACKET_HEADER; j++)
    {
        data[j] = 0;
    }
}

/**
  * @brief  Transmit a data packet using the ymodem protocol
  * @param  data
  * @param  length
  * @retval None
  */
static Ymodem_SendPacket(unsigned char *data, unsigned short length)
{
    unsigned short i;
    i = 0;
    while (i < length)
    {
        ym.putc(data[i]);
        i++;
    }
}



/**
  * @brief  Prepare the data packet
  * @param  timeout
  * @retval None
  */
static void Ymodem_PreparePacket(unsigned char *SourceBuf, unsigned char *data, unsigned char pktNo, unsigned int sizeBlk)
{
    unsigned short i, size, packetSize;
    unsigned char* file_ptr;

    /* Make first three packet */
    packetSize = sizeBlk > PACKET_SIZE ? PACKET_1K_SIZE : PACKET_SIZE;
    size = sizeBlk < packetSize ? sizeBlk :packetSize;
    if (packetSize == PACKET_1K_SIZE)
    {
        data[0] = STX;
    }
    else
    {
        data[0] = SOH;
    }
    data[1] = pktNo;
    data[2] = (~pktNo);
    file_ptr = SourceBuf;

    /* Filename packet has valid data */
    for (i = PACKET_HEADER; i < size + PACKET_HEADER;i++)
    {
        data[i] = *file_ptr++;
    }
    if ( size  <= packetSize)
    {
        for (i = size + PACKET_HEADER; i < packetSize + PACKET_HEADER; i++)
        {
            data[i] = 0x1A; /* EOF (0x1A) or 0x00 */
        }
    }
}

/**
 * @brief send file from the chip to PC
 *          Transmit a file using the ymodem protocol
 * @param buf  Address of the first byte
 * @param sendFileName 
 * @param sizeFile 
 * @return unsigned char  The size of the file
 */
unsigned char ymodem_send (unsigned char *buf, const unsigned char* sendFileName, unsigned int sizeFile)
{
    unsigned char packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD];
    unsigned char FileName[FILE_NAME_LENGTH];
    unsigned char *buf_ptr, tempCheckSum ;
    unsigned short tempCRC, blkNumber;
    unsigned char receivedC[2], CRC16_F = 0, i;
    unsigned int errors = 0, ackReceived = 0, size = 0, pktSize;

    unsigned int YMODEM_TRANSMIT_SIZE = sizeFile;
    
    for (i = 0; i < (FILE_NAME_LENGTH - 1); i++)
    {
        FileName[i] = sendFileName[i];
        
    }
    CRC16_F = 1;

    /* Prepare first block */
    Ymodem_PrepareIntialPacket(&packet_data[0], FileName, &sizeFile);

    do 
    {
        /* Send Packet */
        Ymodem_SendPacket(packet_data, PACKET_SIZE + PACKET_HEADER);

        /* Send CRC or Check Sum based on CRC16_F */
        if (CRC16_F)
        {
            tempCRC = cyg_crc16(&packet_data[3], PACKET_SIZE);
            ym.putc(tempCRC >> 8);
            ym.putc(tempCRC & 0xFF);
        }
        else
        {
            // tempCheckSum = CalChecksum (&packet_data[3], PACKET_SIZE);
            // ym.putc(tempCheckSum);
        }
         receivedC[0] = ym.getc(); 
        /* Wait for Ack and 'C' */
        if (receivedC[0] == ACK)
        { 
            /* Packet transfered correctly */
            ackReceived = 1;
        }
        else
        {
            errors++;
        }
    }while (!ackReceived && (errors < 0x0A));

    if (errors >=  0x0A)
    {
        return errors;
    }
    buf_ptr = buf;
    size = sizeFile;
    blkNumber = 0x01;

    /* Here 1024 bytes package is used to send the packets */
    while (size)
    {
        /* Prepare next packet */
        Ymodem_PreparePacket(buf_ptr, &packet_data[0], blkNumber, size);
        ackReceived = 0;
        receivedC[0]= 0;
        errors = 0;
        do
        {
            /* Send next packet */
            if (size > PACKET_SIZE)
            {
                pktSize = PACKET_1K_SIZE;

            }
            else
            {
                pktSize = PACKET_SIZE;
            }
            Ymodem_SendPacket(packet_data, pktSize + PACKET_HEADER);
            /* Send CRC or Check Sum based on CRC16_F */
            if (CRC16_F)
            {
                tempCRC = cyg_crc16(&packet_data[3], pktSize);
                ym.putc(tempCRC >> 8);
                ym.putc(tempCRC & 0xFF);
            }
            else
            {
                // tempCheckSum = CalChecksum (&packet_data[3], pktSize);
                // ym.putc(tempCheckSum);
            }
            receivedC[0] = ym.getc();
            /* Wait for Ack */
            if (receivedC[0] == ACK)  ////555555
            {    
                
                    ackReceived = 1;  
                    if (size > pktSize)
                    {
                        buf_ptr += pktSize;  
                        size -= pktSize;
                        // if (blkNumber == ((YMODEM_TRANSMIT_SIZE/1024)))
                        // {
                        //     return 0xFF; /*  error */
                        // }
                        // else
                        // {
                            blkNumber++;
                        // }
                    }
                    else
                    {
                        buf_ptr += pktSize;
                        size = 0;
                    }
            }
            else
            {
                errors++;
            }
        }while(!ackReceived && (errors < 0x0A));

        /* Resend packet if NAK  for a count of 10 else end of commuincation */
        if (errors >=  0x0A)
        {
            return errors;
        }

    }
    ackReceived = 0;
    receivedC[0] = 0x00;
    receivedC[1] = 0x00;
    errors = 0;
    do 
    { 
        ym.putc(EOT);   
        /* Send (EOT); */
        /* Wait for Ack */
        receivedC[0] = ym.getc();  // 1111 
        if (receivedC[0] == ACK)
        {
            ackReceived = 1;
        }
        else
        {
            errors++;
        }
       
    }while (!ackReceived && (errors < 0x0A));

    if (errors >=  0x0A)
    {
        return errors;
    }

    /* Last packet preparation */
    ackReceived = 0;
    receivedC[0] = 0x00;
    receivedC[1] = 0x00;
    errors = 0;

    packet_data[0] = SOH;
    packet_data[1] = 0;
    packet_data [2] = 0xFF;

    for (i = PACKET_HEADER; i < (PACKET_SIZE + PACKET_HEADER); i++)
    {
        packet_data [i] = 0x00;
    }

    do 
    {
        /* Send Packet */
        Ymodem_SendPacket(packet_data, PACKET_SIZE + PACKET_HEADER);

        /* Send CRC or Check Sum based on CRC16_F */
        tempCRC = cyg_crc16(&packet_data[3], PACKET_SIZE);
        ym.putc(tempCRC >> 8);
        ym.putc(tempCRC & 0xFF);

        /* Wait for Ack and 'C' */
        receivedC[1] = ym.getc();        
        if (receivedC[1] == ACK) //555555
        {
            ackReceived = 1;  
        }
        else
        {
            errors++;
        }
    }while (!ackReceived && (errors < 0x0A));

    /* Resend packet if NAK  for a count of 10  else end of commuincation */
    if (errors >=  0x0A)
    {
        return errors;
    }  
    receivedC[0] = 0x00;
    do 
    { 
        ym.putc(EOT);   
        /* Send (EOT); */
        /* Wait for Ack */
        receivedC[0] = ym.getc();        
        if (receivedC[0] == ACK) //555555
        {
            ackReceived = 1;  
        }

        else
        {
            errors++;
        }
        /* Clear Overrun flag of the USART2 */
        //USART_ClearFlag(EVAL_COM1, USART_FLAG_ORE);
    }while (!ackReceived && (errors < 0x0A));

    if (errors >=  0x0A)
    {
        return errors;
    }
    return 0; /* file trasmitted successfully */
}


